/*
Copyright (C) 2011 The University of Michigan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Please send inquiries to powertutor@umich.edu
 */

package vn.cybersoft.obs.andriod.batterystats2.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;

import vn.cybersoft.obs.andriod.batterystats2.util.SystemInfo;

public class UMLoggerService extends Service {
	private static final String TAG = "UMLoggerService";

	private static final int NOTIFICATION_ID = 1;
	private static final int NOTIFICATION_ID_LETTER = 2;

	private Thread estimatorThread;
	private PowerEstimator powerEstimator;

	private Notification notification;

	private NotificationManager notificationManager;
	private TelephonyManager phoneManager;

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	@Override
	public void onCreate() {
		powerEstimator = new PowerEstimator(this);

		/* Register to receive phone state messages. */
		phoneManager = (TelephonyManager) this
				.getSystemService(TELEPHONY_SERVICE);
		phoneManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE
				| PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
				| PhoneStateListener.LISTEN_SERVICE_STATE
				| PhoneStateListener.LISTEN_SIGNAL_STRENGTH);

		/* Register to receive airplane mode and battery low messages. */
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
		filter.addAction(Intent.ACTION_BATTERY_LOW);
		filter.addAction(Intent.ACTION_BATTERY_CHANGED);
		filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
		filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
		registerReceiver(broadcastIntentReceiver, filter);

		notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		// android.os.Debug.startMethodTracing("pt.trace");
		
	/*	if (intent.getBooleanExtra("stop", false)) {
			stopSelf();
			return;
		} else*/ if (estimatorThread != null) {
			return;
		}
		//showNotification();
		estimatorThread = new Thread(powerEstimator);
		estimatorThread.start();
	}

	@Override
	public void onDestroy() {
		// android.os.Debug.stopMethodTracing();
		if (estimatorThread != null) {
			estimatorThread.interrupt();
			while (estimatorThread.isAlive()) {
				try {
					estimatorThread.join();
				} catch (InterruptedException e) {
				}
			}
		}
		unregisterReceiver(broadcastIntentReceiver);

/*		
		 * See comments in showNotification() for why we are using reflection
		 * here.
		 
		boolean foregroundSet = false;
		try {
			Method stopForeground = getClass().getMethod("stopForeground",
					boolean.class);
			stopForeground.invoke(this, true);
			foregroundSet = true;
		} catch (InvocationTargetException e) {
		} catch (IllegalAccessException e) {
		} catch (NoSuchMethodException e) {
		}
		if (!foregroundSet) {
			setForeground(false);
			notificationManager.cancel(NOTIFICATION_ID);
		}*/

		super.onDestroy();
	};

/*	*//** This function is to construct the real-time updating notification *//*
	public void showNotification() {
		int icon = R.drawable.level;

		// icon from resources
		CharSequence tickerText = "PowerTutor"; // ticker-text
		long when = System.currentTimeMillis(); // notification time
		Context context = getApplicationContext(); // application Context
		CharSequence contentTitle = "PowerTutor"; // expanded message title
		CharSequence contentText = ""; // expanded message text

		Intent notificationIntent = new Intent(this, UMLogger.class);
		notificationIntent.putExtra("isFromIcon", true);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
				notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
		
		 * the next two lines initialize the Notification, using the
		 * configurations above.
		 
		notification = new Notification(icon, tickerText, when);
		notification.iconLevel = 2;
		notification.setLatestEventInfo(context, contentTitle, contentText,
				contentIntent);

		
		 * We need to set the service to run in the foreground so that system
		 * won't try to destroy the power logging service except in the most
		 * critical situations (which should be fairly rare). Due to differences
		 * in apis across versions of android we have to use reflection. The
		 * newer api simultaneously sets an app to be in the foreground while
		 * adding a notification icon so services can't 'hide' in the
		 * foreground. In the new api the old call, setForeground, does nothing.
		 * See: http://developer.android.com/reference/android/app/Service.html#
		 * startForeground%28int,%20android.app.Notification%29
		 
		boolean foregroundSet = false;
		try {
			Method startForeground = getClass().getMethod("startForeground",
					int.class, Notification.class);
			startForeground.invoke(this, NOTIFICATION_ID, notification);
			foregroundSet = true;
		} catch (InvocationTargetException e) {
		} catch (IllegalAccessException e) {
		} catch (NoSuchMethodException e) {
		}
		if (!foregroundSet) {
			setForeground(true);
			notificationManager.notify(NOTIFICATION_ID, notification);
		}
	}
*/
	/*
	 * This function is to update the notification in real time. This function
	 * is apparently fairly expensive cpu wise. Updating once a second caused a
	 * 8% cpu utilization penalty.
	 */
/*	public void updateNotification(int level, double totalPower) {
		notification.icon = R.drawable.level;
		notification.iconLevel = level;

		// If we know how much charge the battery has left we'll override the
		// normal icon with one that indicates how much time the user can expect
		// left.
		BatteryStats bst = BatteryStats.getInstance();
		if (bst.hasCharge() && bst.hasVoltage()) {
			double charge = bst.getCharge();
			double volt = bst.getVoltage();
			if (charge > 0 && volt > 0) {
				notification.icon = R.drawable.time;

				double minutes = charge * volt / (totalPower / 1000) / 60;
				if (minutes < 55) {
					notification.iconLevel = 1 + (int) Math.max(0,
							Math.round(minutes / 10.0) - 1);
				} else {
					notification.iconLevel = (int) Math.min(13,
							6 + Math.max(0, Math.round(minutes / 60.0) - 1));
				}
			}
		}

		CharSequence contentTitle = "PowerTutor";
		CharSequence contentText = "Total Power: "
				+ (int) Math.round(totalPower) + " mW";

		
		 * When the user selects the notification the tab view for global power
		 * usage will appear.
		 
		Intent notificationIntent = new Intent(this, UMLogger.class);
		notificationIntent.putExtra("isFromIcon", true);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
				notificationIntent, 0);
		notification.setLatestEventInfo(this, contentTitle, contentText,
				contentIntent);
		notificationManager.notify(NOTIFICATION_ID, notification);
	}*/

	private final ICounterService.Stub binder = new ICounterService.Stub() {
		public String[] getComponents() {
			return powerEstimator.getComponents();
		}

		public int[] getComponentsMaxPower() {
			return powerEstimator.getComponentsMaxPower();
		}

		public int getNoUidMask() {
			return powerEstimator.getNoUidMask();
		}

		public int[] getComponentHistory(int count, int componentId, int uid) {
			return powerEstimator.getComponentHistory(count, componentId, uid,
					-1);
		}

		public long[] getTotals(int uid, int windowType) {
			return powerEstimator.getTotals(uid, windowType);
		}

		public long getRuntime(int uid, int windowType) {
			return powerEstimator.getRuntime(uid, windowType);
		}

		public long[] getMeans(int uid, int windowType) {
			return powerEstimator.getMeans(uid, windowType);
		}

		public byte[] getUidInfo(int windowType, int ignoreMask) {
			UidInfo[] infos = powerEstimator.getUidInfo(windowType, ignoreMask);
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			try {
				new ObjectOutputStream(output).writeObject(infos);
			} catch (IOException e) {
				return null;
			}
			for (UidInfo info : infos) {
				info.recycle();
			}
			return output.toByteArray();
		}

		public long getUidExtra(String name, int uid) {
			return powerEstimator.getUidExtra(name, uid);
		}
	};

	BroadcastReceiver broadcastIntentReceiver = new BroadcastReceiver() {
		public void onReceive(Context context, Intent intent) {
			if (intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
				Bundle extra = intent.getExtras();
				try {
					if ((Boolean) extra.get("state")) {
						powerEstimator.writeToLog("airplane-mode on\n");
					} else {
						powerEstimator.writeToLog("airplane-mode off\n");
					}
				} catch (ClassCastException e) {
					// Some people apparently are having this problem. I'm not
					// really
					// sure why this should happen.
					Log.w(TAG, "Couldn't determine airplane mode state");
				}
			} else if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
				powerEstimator.writeToLog("battery low\n");
			} else if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
				powerEstimator.writeToLog("battery-change "
						+ intent.getIntExtra("plugged", -1) + " "
						+ intent.getIntExtra("level", -1) + "/"
						+ intent.getIntExtra("scale", -1) + " "
						+ intent.getIntExtra("voltage", -1)
						+ intent.getIntExtra("temperature", -1) + "\n");
				powerEstimator.plug(intent.getIntExtra("plugged", -1) != 0);
			} else if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)
					|| intent.getAction()
							.equals(Intent.ACTION_PACKAGE_REPLACED)) {
				// A package has either been removed or its metadata has changed
				// and we
				// need to clear the cache of metadata for that app.
				SystemInfo.getInstance().voidUidCache(
						intent.getIntExtra(Intent.EXTRA_UID, -1));
			}
		};
	};

	PhoneStateListener phoneListener = new PhoneStateListener() {
		public void onServiceStateChanged(ServiceState serviceState) {
			switch (serviceState.getState()) {
			case ServiceState.STATE_EMERGENCY_ONLY:
				powerEstimator.writeToLog("phone-service emergency-only\n");
				break;
			case ServiceState.STATE_IN_SERVICE:
				powerEstimator.writeToLog("phone-service in-service\n");
				switch (phoneManager.getNetworkType()) {
				case (TelephonyManager.NETWORK_TYPE_EDGE):
					powerEstimator.writeToLog("phone-network edge\n");
					break;
				case (TelephonyManager.NETWORK_TYPE_GPRS):
					powerEstimator.writeToLog("phone-network GPRS\n");
					break;
				case 8:
					powerEstimator.writeToLog("phone-network HSDPA\n");
					break;
				case (TelephonyManager.NETWORK_TYPE_UMTS):
					powerEstimator.writeToLog("phone-network UMTS\n");
					break;
				default:
					powerEstimator.writeToLog("phone-network "
							+ phoneManager.getNetworkType() + "\n");
				}
				break;
			case ServiceState.STATE_OUT_OF_SERVICE:
				powerEstimator.writeToLog("phone-service out-of-service\n");
				break;
			case ServiceState.STATE_POWER_OFF:
				powerEstimator.writeToLog("phone-service power-off\n");
				break;
			}
		}

		public void onCallStateChanged(int state, String incomingNumber) {
			switch (state) {
			case TelephonyManager.CALL_STATE_IDLE:
				powerEstimator.writeToLog("phone-call idle\n");
				break;
			case TelephonyManager.CALL_STATE_OFFHOOK:
				powerEstimator.writeToLog("phone-call off-hook\n");
				break;
			case TelephonyManager.CALL_STATE_RINGING:
				powerEstimator.writeToLog("phone-call ringing\n");
				break;
			}
		}

		public void onDataConnectionStateChanged(int state) {
			switch (state) {
			case TelephonyManager.DATA_DISCONNECTED:
				powerEstimator.writeToLog("data disconnected\n");
				break;
			case TelephonyManager.DATA_CONNECTING:
				powerEstimator.writeToLog("data connecting\n");
				break;
			case TelephonyManager.DATA_CONNECTED:
				powerEstimator.writeToLog("data connected\n");
				break;
			case TelephonyManager.DATA_SUSPENDED:
				powerEstimator.writeToLog("data suspended\n");
				break;
			}
		}

		public void onSignalStrengthChanged(int asu) {
			powerEstimator.writeToLog("signal " + asu + "\n");
		}
	};
}
